//******************************************************************
//******************************************************************
//**********          ANts Peer To Peer Sources        *************
//
// ANts P2P realizes a third generation P2P net. It protects your
// privacy while you are connected and makes you not trackable, hiding
// your identity (ip) and crypting everything you are sending/receiving
// from others.

// Copyright (C) 2004  Roberto Rossi

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

package ants.p2p.utils.addresses;

import java.util.*;
import java.beans.*;
import java.net.*;
import java.io.*;

import ants.p2p.*;
import ants.p2p.filesharing.*;
import ants.p2p.query.*;

import org.apache.log4j.*;

public class InetAddressEngine extends Thread implements PropertyChangeListener{

  static Logger _logger = Logger.getLogger(InetAddressEngine.class.getName());

  public static int refreshRate = 120000;
  public static int peersToMemorize = 10;
  public static int peersToSendback = 3;
  public static int broadcastTimeToLive = 1000;
  public static InetAddressEngine instance = null;
  public static File trustedPeersFile = new File(WarriorAnt.workingPath + "/trustedPeers.ant");
  public static File peersFile = new File(WarriorAnt.workingPath + "/peers.ant");
  boolean terminate = false;

  WarriorAnt warriorAnt;
  static ArrayList peersWithFreeSlots = new ArrayList();
  static ArrayList trustedPeers = new ArrayList();
  static ArrayList instantMessengersPeers = new ArrayList();

  static{
    if(trustedPeersFile.exists())
      loadTrustedPeers();
    if(peersFile.exists())
      loadPeers();
  }

  public static InetAddressEngine getInstance(WarriorAnt owner){
    try{
      instance = new InetAddressEngine(owner);
      instance.setPriority(1);
      instance.start();
      return instance;
    }catch (Exception ex) {
      _logger.error("",ex);
      return null;
    }
  }

  public static InetAddressEngine getInstance(){
    return instance;
  }

  private InetAddressEngine(WarriorAnt warriorAnt) {
    this.warriorAnt=warriorAnt;
    this.warriorAnt.propertyChangeSupport.addPropertyChangeListener(this);
  }

  public static boolean contains(int[] indexes, int val){
    for(int x = 0; x < indexes.length; x++){
      if(indexes[x]==val)
        return true;
    }
    return false;
  }

  public static ArrayList getRandomIMPeersWithFreeSlots(int howMany){
    int[] indexes = new int[howMany];
    Arrays.fill(indexes,-1);
    for(int x = 0; x < howMany && x < InetAddressEngine.instantMessengersPeers.size(); x++){
      int index = (int)Math.floor(Math.random()*InetAddressEngine.instantMessengersPeers.size());
      while(contains(indexes,index)){
        index = (int)Math.floor(Math.random()*InetAddressEngine.instantMessengersPeers.size());
      }
      indexes[x]=index;
    }
    ArrayList sendBack = new ArrayList();
    for(int x = 0; x < howMany && x < InetAddressEngine.instantMessengersPeers.size(); x++){
      sendBack.add(InetAddressEngine.instantMessengersPeers.get(indexes[x]));
    }
    return sendBack;
  }

  public static ArrayList getRandomPeersWithFreeSlots(int howMany, boolean sendMyAddress){
   int[] indexes = new int[howMany];
   Arrays.fill(indexes,-1);
   for(int x = 0; x < howMany && x < InetAddressEngine.peersWithFreeSlots.size(); x++){
     int index = (int)Math.floor(Math.random()*InetAddressEngine.peersWithFreeSlots.size());
     while(contains(indexes,index)){
       index = (int)Math.floor(Math.random()*InetAddressEngine.peersWithFreeSlots.size());
     }
     indexes[x]=index;
   }
   ArrayList sendBack = new ArrayList();
   for(int x = 0; x < howMany && x < InetAddressEngine.peersWithFreeSlots.size(); x++){
     ServerInfo peer = (ServerInfo) InetAddressEngine.peersWithFreeSlots.get(indexes[x]);
     if (sendMyAddress ||
         ! (peer.getAddress().getHostAddress().equals(ants.p2p.gui.FrameAnt.getInstance(null).getGuiAnt().getConnectionAntPanel().
            getLocalInetAddress().getHostAddress()) && peer.getPort().intValue() ==
            ants.p2p.gui.FrameAnt.getInstance(null).getGuiAnt().getConnectionAntPanel().warriorAnt.getServerPort())) {
       sendBack.add(InetAddressEngine.peersWithFreeSlots.get(indexes[x]));
     }
   }
   _logger.debug("Sending back "+sendBack.size()+" peers["+InetAddressEngine.peersWithFreeSlots.size()+"]");
   return sendBack;
 }

 public static ArrayList getPeersWithFreeSlots(boolean sendMyAddress){
   ArrayList sendBack = getRandomPeersWithFreeSlots(InetAddressEngine.peersToSendback, sendMyAddress);
   return sendBack;
 }

 public static void removeDeadPeer(ServerInfo s){
   if(s != null){
     InetAddressEngine.peersWithFreeSlots.remove(s);
     storePeers();
     _logger.error("Dead peer found in list: "+s.getAddress());
   }else{
     _logger.error("No dead peer found in list: "+s.getAddress());
   }
 }

  public static ArrayList getTrustedPeers(){
    return trustedPeers;
  }

  private static void storeTrustedPeers(){
    try {
      ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
          trustedPeersFile));
      oos.writeObject(trustedPeers);
      oos.close();
    }
    catch (IOException e) {
      _logger.error("Cannot store trusted peers", e);
    }
  }

  private static void storePeers(){
    try {
      ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
          peersFile));
      oos.writeObject(peersWithFreeSlots);
      oos.close();
    }
    catch (IOException e) {
      _logger.error("Cannot store peers", e);
    }
  }

  private static void loadTrustedPeers(){
    try {
      ObjectInputStream ois = new ObjectInputStream(new FileInputStream(trustedPeersFile));
      trustedPeers = (ArrayList) ois.readObject();
      ois.close();
    }catch (Exception e) {
      _logger.error("Cannot load trusted peers");
    }
  }

  private static void loadPeers(){
    try {
      ObjectInputStream ois = new ObjectInputStream(new FileInputStream(peersFile));
      peersWithFreeSlots = (ArrayList) ois.readObject();
      ois.close();
    }catch (Exception e) {
      _logger.error("Cannot load peers");
    }
  }

  public static void removeTrustedPeer(ServerInfo sInfo){
    trustedPeers.remove(sInfo);
    storeTrustedPeers();
  }

  public static void addTrustedPeer(String inetAddr, int port){
    ServerInfo sInfo = new ServerInfo("", inetAddr, new Integer(port), "");
    if(!trustedPeers.contains(sInfo)){
      trustedPeers.add(sInfo);
      storeTrustedPeers();
    }
  }

  public void run(){
    int counter = 0;
    while(!warriorAnt.isDisconnected() && !terminate){
      try {
        _logger.info("InetAddress engine Search...");
        if(counter == 0) warriorAnt.getServersWithFreeSlots();
        warriorAnt.getALanServer();
        sleep(refreshRate);
        counter = (counter + 1) % 3;
      }
      catch (Exception e) {
        _logger.error("",e);
      }
    }
    _logger.info("InetAddressEngine Terminated");
    this.warriorAnt.propertyChangeSupport.removePropertyChangeListener(this);
    this.warriorAnt=null;
  }

  public void terminate(){
    this.terminate = true;
  }

  public void propertyChange(PropertyChangeEvent e) {
    if (e.getPropertyName().equals("inetAddressQueryCompleted")) {
      ArrayList result = (ArrayList) e.getNewValue();
      _logger.debug("Adding peers: "+result.size());
      for (int x = 0; x < result.size(); x++) {
        if (!this.peersWithFreeSlots.contains(result.get(x))) {
          _logger.debug("Added peer: "+result.get(x));
          this.peersWithFreeSlots.add(0, result.get(x));
        }else{
          _logger.debug("Replaced peer: "+result.get(x));
          this.peersWithFreeSlots.remove(result.get(x));
          this.peersWithFreeSlots.add(0, result.get(x));
        }
      }
      while (this.peersWithFreeSlots.size() > InetAddressEngine.peersToMemorize) {
        this.peersWithFreeSlots.remove(this.peersWithFreeSlots.size() - 1);
      }
      storePeers();
    }
    if (e.getPropertyName().equals("updateInstantMessengerPeer")) {
      try{
        ServerInfo si = new ServerInfo("",(String)e.getOldValue(),new Integer((String)e.getNewValue()),"masked");
        this.instantMessengersPeers.add(si);
        _logger.info("Received instant messenger peer address "+e.getOldValue()+":"+e.getNewValue());
      }catch(Exception ex){
        _logger.error("Error updating peer from IM",ex);
      }
    }
    if (e.getPropertyName().equals("removeInstantMessengerPeer")) {
      try{
        for(int x = 0; x < this.instantMessengersPeers.size(); x++){
          ServerInfo si = (ServerInfo)this.instantMessengersPeers.get(x);
          if(si.getAddress().equals(InetAddress.getByName((String)e.getOldValue())))
            this.instantMessengersPeers.remove(e.getOldValue());
        }
        _logger.info("Removed instant messenger peer address "+e.getOldValue());
      }catch(Exception ex){
        _logger.error("Error removing peer from IM",ex);
      }
    }
  }
}
